/**
 * (created at 2011-1-14)
 */
package qiushuo.mysqlparser.recognizer.parser;

import static qiushuo.mysqlparser.recognizer.SQLTokenType.IDENTIFIER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_ALL;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_AND;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_AS;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_BETWEEN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_DISTINCT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_FOR;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_FROM;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_IN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_INTEGER;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_LIKE;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_NOT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_SELECT;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_THEN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.KW_WHEN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.OP_ASSIGN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.PUNC_COMMA;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.PUNC_LEFT_PAREN;
import static qiushuo.mysqlparser.recognizer.SQLTokenType.PUNC_RIGHT_PAREN;

import java.sql.SQLSyntaxErrorException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import qiushuo.mysqlparser.ast.expression.Expression;
import qiushuo.mysqlparser.ast.expression.arithmeic.ArithmeticAddExpression;
import qiushuo.mysqlparser.ast.expression.arithmeic.ArithmeticDivideExpression;
import qiushuo.mysqlparser.ast.expression.arithmeic.ArithmeticIntegerDivideExpression;
import qiushuo.mysqlparser.ast.expression.arithmeic.ArithmeticModExpression;
import qiushuo.mysqlparser.ast.expression.arithmeic.ArithmeticMultiplyExpression;
import qiushuo.mysqlparser.ast.expression.arithmeic.ArithmeticSubtractExpression;
import qiushuo.mysqlparser.ast.expression.arithmeic.MinusExpression;
import qiushuo.mysqlparser.ast.expression.bit.BitAndExpression;
import qiushuo.mysqlparser.ast.expression.bit.BitInvertExpression;
import qiushuo.mysqlparser.ast.expression.bit.BitOrExpression;
import qiushuo.mysqlparser.ast.expression.bit.BitShiftExpression;
import qiushuo.mysqlparser.ast.expression.bit.BitXORExpression;
import qiushuo.mysqlparser.ast.expression.comparison.BetweenAndExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionEqualsExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionGreaterThanExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionGreaterThanOrEqualsExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionIsExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionLessOrGreaterThanExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionLessThanExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionLessThanOrEqualsExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionNotEqualsExpression;
import qiushuo.mysqlparser.ast.expression.comparison.ComparasionNullSafeEqualsExpression;
import qiushuo.mysqlparser.ast.expression.comparison.InExpression;
import qiushuo.mysqlparser.ast.expression.logical.LogicalAndExpression;
import qiushuo.mysqlparser.ast.expression.logical.LogicalNotExpression;
import qiushuo.mysqlparser.ast.expression.logical.LogicalOrExpression;
import qiushuo.mysqlparser.ast.expression.logical.LogicalXORExpression;
import qiushuo.mysqlparser.ast.expression.logical.NegativeValueExpression;
import qiushuo.mysqlparser.ast.expression.misc.AssignmentExpression;
import qiushuo.mysqlparser.ast.expression.misc.InExpressionList;
import qiushuo.mysqlparser.ast.expression.misc.SubqueryAllExpression;
import qiushuo.mysqlparser.ast.expression.misc.SubqueryAnyExpression;
import qiushuo.mysqlparser.ast.expression.primary.CaseValueWhenOperatorExpression;
import qiushuo.mysqlparser.ast.expression.primary.CaseWhenOperatorExpression;
import qiushuo.mysqlparser.ast.expression.primary.ExistsPrimary;
import qiushuo.mysqlparser.ast.expression.primary.Identifier;
import qiushuo.mysqlparser.ast.expression.primary.MatchExpression;
import qiushuo.mysqlparser.ast.expression.primary.ParamMarker;
import qiushuo.mysqlparser.ast.expression.primary.RowExpression;
import qiushuo.mysqlparser.ast.expression.primary.SysVarPrimary;
import qiushuo.mysqlparser.ast.expression.primary.UsrDefVarPrimary;
import qiushuo.mysqlparser.ast.expression.primary.function.FunctionExpression;
import qiushuo.mysqlparser.ast.expression.primary.function.cast.Cast;
import qiushuo.mysqlparser.ast.expression.primary.function.cast.Convert;
import qiushuo.mysqlparser.ast.expression.primary.function.comparison.Interval;
import qiushuo.mysqlparser.ast.expression.primary.function.datetime.Curdate;
import qiushuo.mysqlparser.ast.expression.primary.function.datetime.Curtime;
import qiushuo.mysqlparser.ast.expression.primary.function.datetime.Now;
import qiushuo.mysqlparser.ast.expression.primary.function.groupby.Avg;
import qiushuo.mysqlparser.ast.expression.primary.function.groupby.Count;
import qiushuo.mysqlparser.ast.expression.primary.function.groupby.Max;
import qiushuo.mysqlparser.ast.expression.primary.function.groupby.Min;
import qiushuo.mysqlparser.ast.expression.primary.function.groupby.Sum;
import qiushuo.mysqlparser.ast.expression.primary.function.info.CurrentUser;
import qiushuo.mysqlparser.ast.expression.primary.function.string.Char;
import qiushuo.mysqlparser.ast.expression.primary.function.string.Locate;
import qiushuo.mysqlparser.ast.expression.primary.function.string.Substring;
import qiushuo.mysqlparser.ast.expression.primary.function.string.Trim;
import qiushuo.mysqlparser.ast.expression.primary.literal.IntervalPrimary;
import qiushuo.mysqlparser.ast.expression.primary.literal.LiteralBitField;
import qiushuo.mysqlparser.ast.expression.primary.literal.LiteralBoolean;
import qiushuo.mysqlparser.ast.expression.primary.literal.LiteralHexadecimal;
import qiushuo.mysqlparser.ast.expression.primary.literal.LiteralNull;
import qiushuo.mysqlparser.ast.expression.primary.literal.LiteralNumber;
import qiushuo.mysqlparser.ast.expression.primary.literal.LiteralString;
import qiushuo.mysqlparser.ast.expression.string.LikeExpression;
import qiushuo.mysqlparser.ast.expression.string.RegexpExpression;
import qiushuo.mysqlparser.ast.expression.string.SoundsLikeExpression;
import qiushuo.mysqlparser.ast.expression.type.CastBinaryExpression;
import qiushuo.mysqlparser.ast.expression.type.CollateExpression;
import qiushuo.mysqlparser.recognizer.FunctionManager;
import qiushuo.mysqlparser.recognizer.SQLToken;
import qiushuo.mysqlparser.recognizer.TokenReader;
import qiushuo.mysqlparser.util.Pair;

/**
 * Comment for this class is using ANTLR symbol
 * 
 * @author <a href="mailto:QiuShuo1985@gmail.com">QIU Shuo</a>
 */
public class ExpressionParser extends Parser {
    /**
     * state of field {@link #input} should never be changed during construction
     */
    public ExpressionParser(TokenReader input) {
        super(input);
    }

    // QS_TODO  1, 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟絧arser锟斤拷锟教碉拷锟斤拷锟斤拷锟斤拷锟斤拷锟皆硷拷锟侥憋拷锟�

    /**
     * <code>higherPRJExpr ( ':=' higherPRJExpr )*</code>
     */
    public Expression expression() throws SQLSyntaxErrorException {
        Expression left = logicalOrExpression();
        if (input.isNext(OP_ASSIGN, 0)) {
            input.skipForce(1);
            Expression right = expression();
            return new AssignmentExpression(left, right);
        }
        return left;
    }

    /**
     * <code>higherPRJExpr ( ( '||' | 'OR' ) higherPRJExpr )*</code>
     * 
     * @throws SQLSyntaxErrorException
     */
    private Expression logicalOrExpression() throws SQLSyntaxErrorException {
        Expression expr = logicalXORExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_LOGICAL_OR:
            case KW_OR:
                Expression newExpr = logicalXORExpression();
                expr = new LogicalOrExpression(expr, newExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>higherPRJExpr ( 'XOR' higherPRJExpr )*</code>
     * 
     * @throws SQLSyntaxErrorException
     */
    private Expression logicalXORExpression() throws SQLSyntaxErrorException {
        Expression expr = logicalAndExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case KW_XOR:
                Expression newExpr = logicalAndExpression();
                expr = new LogicalXORExpression(expr, newExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>higherPRJExpr ( ('AND'|'&&') higherPRJExpr )*</code>
     * 
     * @throws SQLSyntaxErrorException
     */
    private Expression logicalAndExpression() throws SQLSyntaxErrorException {
        Expression expr = logicalNotExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_LOGICAL_AND:
            case KW_AND:
                Expression newExpr = logicalNotExpression();
                expr = new LogicalAndExpression(expr, newExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>'NOT'* higherPRJExpr</code>
     * 
     * @throws SQLSyntaxErrorException
     */
    private Expression logicalNotExpression() throws SQLSyntaxErrorException {
        int notNum = 0;
        for (; input.isNext(KW_NOT, notNum); ++notNum);
        input.skipForce(notNum);
        Expression expr = betweenAndExpression();
        for (; notNum > 0; --notNum) {
            expr = new LogicalNotExpression(expr);
        }
        return expr;
    }

    /**
     * <code>higherPRJExpr (('NOT')? 'BETWEEN' higherPRJExpr 'AND' higherPRJExpr)+</code>
     * <br/>
     * "a BETWEEN b AND c BETWEEN d AND e" ==
     * "a BETWEEN b AND (c BETWEEN d AND e)"
     * 
     * @throws SQLSyntaxErrorException
     */
    private Expression betweenAndExpression() throws SQLSyntaxErrorException {
        Expression fst = comparisionExpression();
        boolean not = false;
        if (input.isNext(KW_NOT, 0) && input.isNext(KW_BETWEEN, 1)) {
            input.skipForce(2);
            not = true;
        } else if (input.isNext(KW_BETWEEN, 0)) {
            input.skipForce(1);
        } else {
            return fst;
        }
        Expression snd = comparisionExpression();
        match(KW_AND);
        Expression trd = betweenAndExpression();
        return new BetweenAndExpression(not, fst, snd, trd);
    }

    private Expression comparisionExpression() throws SQLSyntaxErrorException {
        Expression expr = bitOrExpression();
        loop1: for (SQLToken token; (token = input.next()) != null;) {
            Expression newExpr = null;
            switch (token.getType()) {
            case OP_EQUALS:
                newExpr = anyAllExpression();
                expr = new ComparasionEqualsExpression(expr, newExpr);
                break;
            case OP_NULL_SAFE_EQUALS:
                newExpr = bitOrExpression();
                expr = new ComparasionNullSafeEqualsExpression(expr, newExpr);
                break;
            case OP_GREATER_OR_EQUALS:
                newExpr = anyAllExpression();
                expr = new ComparasionGreaterThanOrEqualsExpression(expr, newExpr);
                break;
            case OP_GREATER_THAN:
                newExpr = anyAllExpression();
                expr = new ComparasionGreaterThanExpression(expr, newExpr);
                break;
            case OP_LESS_OR_EQUALS:
                newExpr = anyAllExpression();
                expr = new ComparasionLessThanOrEqualsExpression(expr, newExpr);
                break;
            case OP_LESS_THAN:
                newExpr = anyAllExpression();
                expr = new ComparasionLessThanExpression(expr, newExpr);
                break;
            case OP_LESS_OR_GREATER:
                newExpr = anyAllExpression();
                expr = new ComparasionLessOrGreaterThanExpression(expr, newExpr);
                break;
            case OP_NOT_EQUALS:
                newExpr = anyAllExpression();
                expr = new ComparasionNotEqualsExpression(expr, newExpr);
                break;
            case KW_IS:
                token = input.next();
                if (token == null) {
                    input.skipForce(-1);
                    break loop1;
                }
                switch (token.getType()) {
                case LITERAL_NULL:
                    expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_NULL);
                    break;
                case LITERAL_BOOL_TRUE:
                    expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_TRUE);
                    break;
                case LITERAL_BOOL_FALSE:
                    expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_FALSE);
                    break;
                case IDENTIFIER:
                    if (token.getText() != null && "UNKNOWN".equals(token.getText().toUpperCase())) {
                        expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_UNKNOWN);
                        break;
                    } else {
                        input.skipForce(-2);
                        break loop1;
                    }
                case KW_NOT:
                    token = input.next();
                    if (token == null) {
                        input.skipForce(-2);
                        break loop1;
                    }
                    switch (token.getType()) {
                    case LITERAL_NULL:
                        expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_NOT_NULL);
                        break;
                    case LITERAL_BOOL_TRUE:
                        expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_NOT_TRUE);
                        break;
                    case LITERAL_BOOL_FALSE:
                        expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_NOT_FALSE);
                        break;
                    case IDENTIFIER:
                        if (token.getText() != null && "UNKNOWN".equals(token.getText().toUpperCase())) {
                            expr = new ComparasionIsExpression(expr, ComparasionIsExpression.IS_NOT_UNKNOWN);
                            break;
                        } else {
                            input.skipForce(-3);
                            break loop1;
                        }
                    default:
                        input.skipForce(-3);
                        break loop1;
                    }
                    break;
                default:
                    input.skipForce(-2);
                    break loop1;
                }
                break;
            case KW_NOT:
                token = input.next();
                if (token == null) {
                    input.skipForce(-1);
                    break loop1;
                }
                switch (token.getType()) {
                case KW_IN:
                    Expression rightOperand4In = rightOprandOfIn();
                    expr = new InExpression(true, expr, rightOperand4In);
                    break;
                case KW_LIKE:
                    Expression pat = bitOrExpression();
                    Expression escape = null;
                    if (input.isNext(IDENTIFIER, 0) && input.isNext(0, true, "ESCAPE")) {
                        input.skipForce(1);
                        escape = bitOrExpression();
                    }
                    expr = new LikeExpression(true, expr, pat, escape);
                    break;
                case KW_RLIKE:
                case KW_REGEXP:
                    Expression pattern = bitOrExpression();
                    expr = new RegexpExpression(true, expr, pattern);
                    break;
                default:
                    input.skipForce(-2);
                    break loop1;
                }
                break;
            case KW_RLIKE:
            case KW_REGEXP:
                Expression pattern = bitOrExpression();
                expr = new RegexpExpression(false, expr, pattern);
                break;
            case KW_LIKE:
                Expression pat = bitOrExpression();
                Expression escape = null;
                if (input.isNext(IDENTIFIER, 0) && input.isNext(0, true, "ESCAPE")) {
                    input.skipForce(1);
                    escape = bitOrExpression();
                }
                expr = new LikeExpression(false, expr, pat, escape);
                break;
            case KW_IN:
                Expression rightOperand4In = rightOprandOfIn();
                if (rightOperand4In == null) {
                    input.backForce();
                    break loop1;
                }
                expr = new InExpression(false, expr, rightOperand4In);
                break;
            case IDENTIFIER:
                if (token.getText() != null && "SOUNDS".equals(token.getText())) {
                    token = input.next();
                    if (token == null) {
                        input.backForce();
                        break loop1;
                    }
                    if (!KW_LIKE.equals(token.getType())) {
                        input.skipForce(-2);
                        break loop1;
                    }
                    Expression expr2 = bitOrExpression();
                    expr = new SoundsLikeExpression(expr, expr2);
                    break;
                } else {
                    input.backForce();
                    break loop1;
                }
            default:
                input.backForce();
                break loop1;
            }
        }
        return expr;
    }

    // QS_TODO part of comparison operator
    private Expression anyAllExpression() throws SQLSyntaxErrorException {
        Expression expr = null;
        if (input.isNext(KW_ALL, 0)) {
            input.skipForce(1);
            match(PUNC_LEFT_PAREN);
            expr = subquery();
            match(PUNC_RIGHT_PAREN);
            return new SubqueryAllExpression(expr);
        } else if (input.isNext(0, true, "ANY", "SOME")) {
            input.skipForce(1);
            match(PUNC_LEFT_PAREN);
            expr = subquery();
            match(PUNC_RIGHT_PAREN);
            return new SubqueryAnyExpression(expr);
        } else {
            return bitOrExpression();
        }
    }

    /**
     * return null if unsuccessful. if null, then back trace. (mark in side)
     * 
     * @return right operand of IN. e.g. "(1,2,3)" for "id in (1,2,3)". Query or
     *         InParamList
     */
    private Expression rightOprandOfIn() throws SQLSyntaxErrorException {
        if (!input.hasNext(2)) {
            return null;
        }
        SQLToken token = input.next();
        if (!PUNC_LEFT_PAREN.equals(token.getType())) {
            input.backForce();
            return null;
        }
        if (input.isNext(KW_SELECT, 0)) {
            Expression subquery = subquery();
            match(PUNC_RIGHT_PAREN);
            return subquery;
        }
        return new InExpressionList(expressionList());
    }

    private Expression bitOrExpression() throws SQLSyntaxErrorException {
        Expression expr = bitAndExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_VERTICAL_BAR:
                Expression newExpr = bitAndExpression();
                expr = new BitOrExpression(expr, newExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    private Expression bitAndExpression() throws SQLSyntaxErrorException {
        Expression expr = bitShiftExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_AMPERSAND:
                Expression newExpr = bitShiftExpression();
                expr = new BitAndExpression(expr, newExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>higherExpr ( ('&lt;&lt;'|'&gt;&gt;') higherExpr)+</code>
     */
    private Expression bitShiftExpression() throws SQLSyntaxErrorException {
        Expression expr = arithmeticTermOperatorExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_LEFT_SHIFT:
                Expression leftSftExpr = arithmeticTermOperatorExpression();
                expr = new BitShiftExpression(false, expr, leftSftExpr);
                break;
            case OP_RIGHT_SHIFT:
                Expression rightSftExpr = arithmeticTermOperatorExpression();
                expr = new BitShiftExpression(true, expr, rightSftExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>higherExpr ( ('+'|'-') higherExpr)+</code>
     */
    private Expression arithmeticTermOperatorExpression() throws SQLSyntaxErrorException {
        Expression expr = arithmeticFactorOperatorExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_PLUS:
                Expression addeeExpr = arithmeticFactorOperatorExpression();
                expr = new ArithmeticAddExpression(expr, addeeExpr);
                break;
            case OP_MINUS:
                Expression subtracteeExpr = arithmeticFactorOperatorExpression();
                expr = new ArithmeticSubtractExpression(expr, subtracteeExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>higherExpr ( ('*'|'/'|'%'|'DIV'|'MOD') higherExpr)+</code>
     */
    private Expression arithmeticFactorOperatorExpression() throws SQLSyntaxErrorException {
        Expression expr = bitXORExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            Expression rightFactor = null;
            switch (token.getType()) {
            case OP_ASTERISK:
                rightFactor = bitXORExpression();
                expr = new ArithmeticMultiplyExpression(expr, rightFactor);
                break;
            case OP_SLASH:
                rightFactor = bitXORExpression();
                expr = new ArithmeticDivideExpression(expr, rightFactor);
                break;
            case KW_DIV:
                rightFactor = bitXORExpression();
                expr = new ArithmeticIntegerDivideExpression(expr, rightFactor);
                break;
            case OP_PERCENT:
            case KW_MOD:
                rightFactor = bitXORExpression();
                expr = new ArithmeticModExpression(expr, rightFactor);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>higherExpr ('^' higherExpr)+</code>
     */
    private Expression bitXORExpression() throws SQLSyntaxErrorException {
        Expression expr = unaryOpExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            switch (token.getType()) {
            case OP_CARET:
                Expression newExpr = unaryOpExpression();
                expr = new BitXORExpression(expr, newExpr);
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    /**
     * <code>('-'|'~'|'!'|'BINARY')* higherExpr</code><br/>
     * '!' has higher precedence
     */
    private Expression unaryOpExpression() throws SQLSyntaxErrorException {
        SQLToken token = input.next();
        if (token == null) {
            err("first token of unary operator expr is EOF");
        }
        Expression expr = null;
        switch (token.getType()) {
        case OP_EXCLAMATION:
            expr = unaryOpExpression();
            return new NegativeValueExpression(expr);
        case OP_MINUS:
            expr = unaryOpExpression();
            return new MinusExpression(expr);
        case OP_TILDE:
            expr = unaryOpExpression();
            return new BitInvertExpression(expr);
        case KW_BINARY:
            expr = unaryOpExpression();
            return new CastBinaryExpression(expr);
        default:
            input.backForce();
            return collateExpression();
        }
    }

    private Expression collateExpression() throws SQLSyntaxErrorException {
        Expression expr = primaryExpression();
        for (SQLToken token = null; (token = input.next()) != null;) {
            SQLToken collateName = null;
            switch (token.getType()) {
            case KW_COLLATE:
                collateName = match(IDENTIFIER);
                expr = new CollateExpression(expr, collateName.getText());
                break;
            default:
                input.backForce();
                return expr;
            }
        }
        return expr;
    }

    private Expression primaryExpression() throws SQLSyntaxErrorException {
        List<Expression> tempExprList = null;
        Expression tempExpr = null;
        SQLToken temp = null;
        SQLToken token = input.next();
        if (token == null) {
            err("first token of primary is EOF");
        }
        switch (token.getType()) {
        case LITERAL_BIT_FIELD:
            return new LiteralBitField(token.getText());
        case LITERAL_HEXADECIMAL:
            return new LiteralHexadecimal(token.getText());
        case LITERAL_BOOL_FALSE:
            return new LiteralBoolean(false);
        case LITERAL_BOOL_TRUE:
            return new LiteralBoolean(true);
        case LITERAL_NULL:
            return new LiteralNull();
        case LITERAL_STRING:
            return new LiteralString(token.getText());
        case LITERAL_NUMBER:
            return new LiteralNumber(token.getText());
        case SC_QUESTION:
            return new ParamMarker();
        case KW_CASE:
            return caseWhenExpression();
        case KW_INTERVAL:
            return intervalExpression();
        case KW_EXISTS:
            match(PUNC_LEFT_PAREN);
            Expression subquery4Exists = subquery();
            match(PUNC_RIGHT_PAREN);
            return new ExistsPrimary(subquery4Exists);
        case USER_DEFINED_VARIABLE:
            return new UsrDefVarPrimary(token.getText());
        case SYSTEM_VARIABLE:
            return new SysVarPrimary(token.getText());
        case KW_MATCH:
            return matchExpression();
        case PUNC_LEFT_PAREN:
            Expression inParen = null;
            if (input.isNext(KW_SELECT, 0)) {
                inParen = subquery();
            } else {
                inParen = expression();
                if (input.isNext(PUNC_COMMA, 0)) {
                    input.skipForce(1);
                    tempExprList = new LinkedList<Expression>();
                    tempExprList.add(inParen);
                    tempExprList = expressionList(tempExprList);
                    return new RowExpression(tempExprList);
                }
            }
            match(PUNC_RIGHT_PAREN);
            return inParen;
        case KW_CURRENT_DATE:
            if (input.isNext(PUNC_LEFT_PAREN, 0) && input.isNext(PUNC_RIGHT_PAREN, 1)) {
                input.skipForce(2);
            }
            return new Curdate(null);
        case KW_CURRENT_TIME:
            if (input.isNext(PUNC_LEFT_PAREN, 0) && input.isNext(PUNC_RIGHT_PAREN, 1)) {
                input.skipForce(2);
            }
            return new Curtime(null);
        case KW_CURRENT_TIMESTAMP:
        case KW_LOCALTIME:
        case KW_LOCALTIMESTAMP:
            if (input.isNext(PUNC_LEFT_PAREN, 0) && input.isNext(PUNC_RIGHT_PAREN, 1)) {
                input.skipForce(2);
            }
            return new Now(null);
        case KW_CURRENT_USER:
            if (input.isNext(PUNC_LEFT_PAREN, 0) && input.isNext(PUNC_RIGHT_PAREN, 1)) {
                input.skipForce(2);
            }
            return new CurrentUser(null);
        case KW_DATABASE:
        case KW_DEFAULT:
        case KW_IF:
        case KW_INSERT:
        case KW_LEFT:
        case KW_REPEAT:
        case KW_REPLACE:
        case KW_RIGHT:
        case KW_SCHEMA:
        case KW_UTC_DATE:
        case KW_UTC_TIME:
        case KW_UTC_TIMESTAMP:
        case KW_VALUES:
            if (!input.isNext(PUNC_LEFT_PAREN, 0)) {
                throw err("keyword not followed by '(' is not expression: " + token);
            }
            return ordinaryFunction(FunctionManager.getInstance(), token.getText(), token.getText().toUpperCase());
        case KW_MOD:
            match(PUNC_LEFT_PAREN);
            Expression modLeft = expression();
            match(PUNC_COMMA);
            Expression modRight = expression();
            match(PUNC_RIGHT_PAREN);
            return new ArithmeticModExpression(modLeft, modRight);
        case KW_CHAR:
            match(PUNC_LEFT_PAREN);
            tempExprList = new LinkedList<Expression>();
            while (true) {
                Expression arg = expression();
                tempExprList.add(arg);
                temp = input.next();
                if (temp == null) {
                    throw err("expect ',' or 'USING' or ')' but is null");
                }
                switch (temp.getType()) {
                case PUNC_COMMA:
                    continue;
                case PUNC_RIGHT_PAREN:
                    return new Char(tempExprList, null);
                case KW_USING:
                    temp = match(IDENTIFIER);
                    match(PUNC_RIGHT_PAREN);
                    return new Char(tempExprList, temp.getText());
                default:
                    throw err("expect ',' or 'USING' or ')' but is " + temp);
                }
            }
        case KW_CONVERT:
            match(PUNC_LEFT_PAREN);
            Expression convertExpr = expression();
            temp = input.next();
            if (temp == null) {
                throw err("expect ',' or 'USING' but is " + temp);
            }
            switch (temp.getType()) {
            case PUNC_COMMA: // ODBC syntax
                Pair<String, Pair<Expression, Expression>> type = type4specialFunc();
                match(PUNC_RIGHT_PAREN);
                Pair<Expression, Expression> info = type.getSecond();
                if (info != null) {
                    return new Convert(convertExpr, type.getFirst(), info.getFirst(), info.getSecond());
                } else {
                    return new Convert(convertExpr, type.getFirst(), null, null);
                }
            case KW_USING:
                temp = match(IDENTIFIER);
                match(PUNC_RIGHT_PAREN);
                return new Convert(convertExpr, temp.getText());
            default:
                throw err("expect ',' or 'USING' but is " + temp);
            }
        case IDENTIFIER:
            String idText = token.getText();
            if (!input.isNext(PUNC_LEFT_PAREN, 0)) {
                return new Identifier(idText);
            }
            // function
            if (idText == null) {
                err("identifier with a null text");
            }
            boolean tempGroupDistinct = false;
            String idTextUpper = idText.toUpperCase();
            FunctionManager functionUtil = FunctionManager.getInstance();
            switch (functionUtil.getParsingStrategy(idTextUpper)) {
            case CAST:
                input.skipForce(1);
                tempExpr = expression();
                match(KW_AS);
                Pair<String, Pair<Expression, Expression>> type = type4specialFunc();
                match(PUNC_RIGHT_PAREN);
                Pair<Expression, Expression> info = type.getSecond();
                if (info != null) {
                    return new Cast(tempExpr, type.getFirst(), info.getFirst(), info.getSecond());
                } else {
                    return new Cast(tempExpr, type.getFirst(), null, null);
                }
            case POSITION:
                tempExprList = new ArrayList<Expression>(2);
                input.skipForce(1);
                tempExprList.add(expression());
                match(KW_IN);
                tempExprList.add(expression());
                match(PUNC_RIGHT_PAREN);
                return new Locate(tempExprList);
            case SUBSTRING:
                tempExprList = new ArrayList<Expression>(3);
                input.skipForce(1);
                tempExprList.add(expression());
                if (input.isNext(PUNC_COMMA, 0) || input.isNext(KW_FROM, 0)) {
                    input.skipForce(1);
                } else {
                    throw err("at least two arguments for function of substring");
                }
                tempExprList.add(expression());
                if (input.isNext(PUNC_COMMA, 0) || input.isNext(KW_FOR, 0)) {
                    tempExprList.add(expression());
                }
                match(PUNC_RIGHT_PAREN);
                return new Substring(tempExprList);
            case ROW:
                input.skipForce(1);
                tempExprList = expressionList();
                return new RowExpression(tempExprList);
            case TRIM:
                input.skipForce(1);
                temp = input.next();
                if (temp == null) {
                    throw err("unexpected null for function of trim");
                }
                Expression remstr = null;
                String direction = null;
                switch (temp.getType()) {
                case KW_BOTH:
                case KW_LEADING:
                case KW_TRAILING:
                    direction = temp.getText();
                    break;
                default:
                    input.backForce();
                }
                if (direction != null) {
                    if (input.isNext(KW_FROM, 0)) {
                        input.skipForce(1);
                        tempExpr = expression();
                        match(PUNC_RIGHT_PAREN);
                        return new Trim(direction, null, tempExpr);
                    } else {
                        remstr = expression();
                        match(KW_FROM);
                        tempExpr = expression();
                        match(PUNC_RIGHT_PAREN);
                        return new Trim(direction, remstr, tempExpr);
                    }
                }
                tempExpr = expression();
                if (input.isNext(KW_FROM, 0)) {
                    remstr = tempExpr;
                    input.skipForce(1);
                    tempExpr = expression();
                    match(PUNC_RIGHT_PAREN);
                    return new Trim(null, remstr, tempExpr);
                }
                match(PUNC_RIGHT_PAREN);
                return new Trim(null, null, tempExpr);
            case AVG:
                input.skipForce(1);
                if (input.isNext(KW_DISTINCT, 0)) {
                    input.skipForce(1);
                    tempGroupDistinct = true;
                }
                tempExpr = expression();
                match(PUNC_RIGHT_PAREN);
                return new Avg(tempExpr, tempGroupDistinct);
            case MAX:
                input.skipForce(1);
                if (input.isNext(KW_DISTINCT, 0)) {
                    input.skipForce(1);
                    tempGroupDistinct = true;
                }
                tempExpr = expression();
                match(PUNC_RIGHT_PAREN);
                return new Max(tempExpr, tempGroupDistinct);
            case MIN:
                input.skipForce(1);
                if (input.isNext(KW_DISTINCT, 0)) {
                    input.skipForce(1);
                    tempGroupDistinct = true;
                }
                tempExpr = expression();
                match(PUNC_RIGHT_PAREN);
                return new Min(tempExpr, tempGroupDistinct);
            case SUM:
                input.skipForce(1);
                if (input.isNext(KW_DISTINCT, 0)) {
                    input.skipForce(1);
                    tempGroupDistinct = true;
                }
                tempExpr = expression();
                match(PUNC_RIGHT_PAREN);
                return new Sum(tempExpr, tempGroupDistinct);
            case COUNT:
                input.skipForce(1);
                temp = input.next();
                if (temp == null) {
                    throw err("unexpected null for function of count");
                }
                switch (temp.getType()) {
                case KW_DISTINCT:
                    tempExprList = new LinkedList<Expression>();
                    while (true) {
                        tempExpr = expression();
                        tempExprList.add(tempExpr);
                        temp = input.next();
                        if (temp == null) {
                            throw err("unexpected null for function of count");
                        }
                        switch (temp.getType()) {
                        case PUNC_COMMA:
                            continue;
                        case PUNC_RIGHT_PAREN:
                            return new Count(tempExprList);
                        default:
                            throw err("unexpected token for function of count: " + temp);
                        }
                    }
                case OP_ASTERISK:
                    match(PUNC_RIGHT_PAREN);
                    return new Count();
                default:
                    input.backForce();
                    tempExpr = expression();
                    match(PUNC_RIGHT_PAREN);
                    return new Count(tempExpr);
                }
            case _DEFAULT:
                return ordinaryFunction(functionUtil, idText, idTextUpper);
            default:
                throw err("unexpected function parsing strategy for id of " + idText);
            }
        default:
            throw err("unrecognized token for primary");
        }
    }

    private static final Set<String> type4specialFuncSet = initType4specialFunc();

    private static Set<String> initType4specialFunc() {
        Set<String> set = new HashSet<String>();
        set.add("DATE");
        set.add("DATETIME");
        set.add("SIGNED");
        set.add("TIME");
        return set;
    }

    private Pair<String, Pair<Expression, Expression>> type4specialFunc() throws SQLSyntaxErrorException {
        Expression exp1 = null;
        Expression exp2 = null;
        SQLToken type = input.next();
        if (type == null) {
            throw err("null type name");
        }
        switch (type.getType()) {
        case KW_BINARY:
        case KW_CHAR:
            if (input.isNext(PUNC_LEFT_PAREN, 0)) {
                input.skipForce(1);
                exp1 = expression();
                match(PUNC_RIGHT_PAREN);
                return new Pair<String, Pair<Expression, Expression>>(type.getText(), new Pair<Expression, Expression>(
                        exp1,
                        exp2));
            } else {
                return new Pair<String, Pair<Expression, Expression>>(type.getText(), null);
            }
        case KW_DECIMAL:
            if (input.isNext(PUNC_LEFT_PAREN, 0)) {
                input.skipForce(1);
                exp1 = expression();
                if (input.isNext(PUNC_COMMA, 0)) {
                    input.skipForce(1);
                    exp2 = expression();
                }
                match(PUNC_RIGHT_PAREN);
                return new Pair<String, Pair<Expression, Expression>>(type.getText(), new Pair<Expression, Expression>(
                        exp1,
                        exp2));
            } else {
                return new Pair<String, Pair<Expression, Expression>>(type.getText(), null);
            }
        case KW_UNSIGNED:
            if (input.isNext(KW_INTEGER, 0)) {
                input.skipForce(1);
            }
            return new Pair<String, Pair<Expression, Expression>>(type.getText(), null);
        case IDENTIFIER:
            String idup = type.getText().toUpperCase();
            if (type4specialFuncSet.contains(idup)) {
                if ("SIGNED".equals(idup)) {
                    if (input.isNext(KW_INTEGER, 0)) {
                        input.skipForce(1);
                    }
                }
                return new Pair<String, Pair<Expression, Expression>>(type.getText(), null);
            } else {
                throw err("invalide type name: " + type);
            }
        default:
            throw err("invalide type name: " + type);
        }
    }

    /**
     * first <code>functionName</code> has already been consumed
     */
    private Expression ordinaryFunction(FunctionManager functionUtil, String idText, String idTextUpper)
            throws SQLSyntaxErrorException {
        if (!functionUtil.isOrdinaryFunction(idTextUpper)) {
            return new Identifier(idText);
        }
        match(PUNC_LEFT_PAREN);
        List<Expression> args = expressionList();
        FunctionExpression funcExpr = functionUtil.createFunctionExpression(idTextUpper, args);
        if (funcExpr == null) {
            throw err("unrecognized function " + idText);
        }
        return funcExpr;
    }

    /**
     * first <code>MATCH</code> has already been consumed before invocation
     */
    private MatchExpression matchExpression() throws SQLSyntaxErrorException {
        SQLToken token = null;
        match(PUNC_LEFT_PAREN);
        List<String> colList = new LinkedList<String>();
        loopMatchCol: while (true) {
            if (!input.hasNext(2)) {
                err("at least 2+ tokens left in match expression");
            }
            SQLToken tk = match(IDENTIFIER);
            colList.add(tk.getText());
            tk = input.next();
            switch (tk.getType()) {
            case PUNC_COMMA:
                continue;
            case PUNC_RIGHT_PAREN:
                break loopMatchCol;
            default:
                err("expect ',' or ')' in match column list");
                break;
            }
        }
        match(IDENTIFIER, true, "AGAINST");
        match(PUNC_LEFT_PAREN);
        Expression matchPattern = expression();
        token = input.next();
        if (token == null) {
            err("expect ')' or searchModifier for match expression but is null");
        }
        switch (token.getType()) {
        case PUNC_RIGHT_PAREN:
            return new MatchExpression(colList, matchPattern, null);
        case KW_WITH:
            match(IDENTIFIER);
            match(IDENTIFIER);
            match(PUNC_RIGHT_PAREN);
            return new MatchExpression(colList, matchPattern, MatchExpression.Modifier.WITH_QUERY_EXPANSION);
        case KW_IN:
            token = input.next();
            if (token == null) {
                err("unrecognized search mode name for match expression");
            }
            switch (token.getType()) {
            case KW_NATURAL:
                match(IDENTIFIER, true, "LANGUAGE");
                match(IDENTIFIER, true, "MODE");
                token = input.next();
                if (token == null) {
                    err("unrecognized search mode name for match expression");
                }
                switch (token.getType()) {
                case PUNC_RIGHT_PAREN:
                    return new MatchExpression(colList, matchPattern, MatchExpression.Modifier.IN_NATURAL_LANGUAGE_MODE);
                default:
                    match(IDENTIFIER, true, "QUERY");
                    match(IDENTIFIER, true, "EXPANSION");
                    match(PUNC_RIGHT_PAREN);
                    return new MatchExpression(
                            colList,
                            matchPattern,
                            MatchExpression.Modifier.IN_NATURAL_LANGUAGE_MODE_WITH_QUERY_EXPANSION);
                }
            default:
                match(IDENTIFIER, true, "MODE");
                match(PUNC_RIGHT_PAREN);
                return new MatchExpression(colList, matchPattern, MatchExpression.Modifier.IN_BOOLEAN_MODE);
            }
        default:
            throw err("unrecognized search mode name for match expression");
        }
    }

    /**
     * first <code>INTERVAL</code> has already been consumed before invocation
     */
    private Expression intervalExpression() throws SQLSyntaxErrorException {
        boolean paren = input.isNext(PUNC_LEFT_PAREN, 0);
        Expression expr;
        List<Expression> exprList = null;
        if (paren) {
            input.skipForce(1);
            if (input.isNext(KW_SELECT, 0)) {
                expr = subquery();
                match(PUNC_RIGHT_PAREN);
                paren = false;
            } else {
                exprList = expressionList();
                if (exprList.size() > 1) {
                    return new Interval(exprList);
                }
                expr = exprList.get(0);
            }
        } else {
            expr = expression();
        }
        SQLToken token = input.next();
        if (token == null) {
            if (paren) {
                return new Interval(exprList);
            } else {
                err("interval should end with unit");
            }
        }
        switch (token.getType()) {
        case KW_SECOND_MICROSECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.SECOND_MICROSECOND);
        case KW_MINUTE_MICROSECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.MINUTE_MICROSECOND);
        case KW_MINUTE_SECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.MINUTE_SECOND);
        case KW_HOUR_MICROSECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.HOUR_MICROSECOND);
        case KW_HOUR_SECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.HOUR_SECOND);
        case KW_HOUR_MINUTE:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.HOUR_MINUTE);
        case KW_DAY_MICROSECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.DAY_MICROSECOND);
        case KW_DAY_SECOND:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.DAY_SECOND);
        case KW_DAY_MINUTE:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.DAY_MINUTE);
        case KW_DAY_HOUR:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.DAY_HOUR);
        case KW_YEAR_MONTH:
            return new IntervalPrimary(expr, IntervalPrimary.Unit.YEAR_MONTH);
        case IDENTIFIER:
            String unitText = token.getText();
            IntervalPrimary.Unit unit = IntervalPrimary.getIntervalUnit(unitText.toUpperCase());
            if (unit != null) {
                return new IntervalPrimary(expr, unit);
            } else if (paren) {
                input.backForce();
                return new Interval(exprList);
            } else {
                err("interval should end with unit, not " + token);
            }
        default:
            if (paren) {
                input.backForce();
                return new Interval(exprList);
            } else {
                throw err("interval should end with unit, not " + token);
            }
        }
    }

    /**
     * first <code>CASE</code> has already been consumed before invocation
     */
    private Expression caseWhenExpression() throws SQLSyntaxErrorException {
        boolean caseValueStyle = false;
        Expression value = null;
        if (input.isNext(KW_WHEN, 0)) {
            input.skipForce(1);
        } else {
            value = expression();
            caseValueStyle = true;
            match(KW_WHEN);
        }
        List<Pair<Expression, Expression>> caseList = new LinkedList<Pair<Expression, Expression>>();
        Expression elseResult = null;
        loop1: while (true) {
            Expression cond = expression();
            match(KW_THEN);
            Expression result = expression();
            caseList.add(new Pair<Expression, Expression>(cond, result));
            SQLToken token = input.next();
            if (token == null) {
                err("followed must be END or WHEN or ELSE, but is null");
            }
            switch (token.getType()) {
            case KW_WHEN:
                continue;
            case IDENTIFIER:
                if (token.getText() == null || !"END".equals(token.getText().toUpperCase())) {
                    err("followed must be END or WHEN or ELSE, but is " + token);
                } else {
                    break loop1;
                }
            case KW_ELSE:
                elseResult = expression();
                match(IDENTIFIER, true, "END");
                break loop1;
            default:
                err("followed must be END or WHEN or ELSE, but is " + token);
            }
        }
        if (caseValueStyle) {
            return new CaseValueWhenOperatorExpression(value, caseList, elseResult);
        } else {
            return new CaseWhenOperatorExpression(caseList, elseResult);
        }
    }

    /**
     * first <code>'('</code> has been consumed. At least one element. Consume
     * last ')' after invocation <br/>
     * <code>'(' expr (',' expr)* ')'</code>
     */
    private List<Expression> expressionList(List<Expression> exprList) throws SQLSyntaxErrorException {
        while (true) {
            Expression expr = expression();
            exprList.add(expr);
            SQLToken token = input.next();
            if (token == null) {
                err("expect ',' or ')' but is null");
            }
            switch (token.getType()) {
            case PUNC_COMMA:
                continue;
            case PUNC_RIGHT_PAREN:
                return exprList;
            default:
                err("expect ',' or ')' but is " + token);
            }
        }
    }

    private List<Expression> expressionList() throws SQLSyntaxErrorException {
        return expressionList(new LinkedList<Expression>());
    }

    /**
     * start from SELECT
     */
    private Expression subquery() throws SQLSyntaxErrorException {
        return new DMLSelectStmtParser(input).selectUnionStmt();
    }
}
